package vm;

import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import os.*;
import memory.*;
/**
 * Procesoriaus imitacija, darbas su atmintimis, komandų vykdymas, adresavimas
 * @author Paulius Dambrauskas, Šarūnas Kūjalis
 */
public class Processor {
    
    private ObservableList<Register> registerArray;
    private MainWindowController main;

    
    /**
     * Konstruktorius: priskiria pradines registrų reikšmes, sukuria atmintį
     * @author Šarūnas Kūjalis
     * @param main interfeiso kontroleris
     */
    public Processor(MainWindowController main) {
        this.main = main;
        ArrayList<Register> register = new ArrayList<>();
        registerArray = FXCollections.observableList(register);
        registerArray.add(new Register("MODE" , VM.mode, 1));
        registerArray.add(new Register("PTR" , VM.pageTableStart/16));
        registerArray.add(new Register("PI" , 0, 1));
        registerArray.add(new Register("TI" , 0, 1));
        registerArray.add(new Register("SI" , 0, 1));
        registerArray.add(new Register("PC" , 0, 2));
        registerArray.add(new Register("SP" , 0));
        registerArray.add(new Register("R" , 0));
        registerArray.add(new Register("C" , 0, 1));
        registerArray.add(new Register("CH[1]" , 0, 1));
        registerArray.add(new Register("CH[2]" , 0, 1));
        registerArray.add(new Register("CH[3]" , 0, 1));
        registerArray.add(new Register("CH[4]" , 0, 1));
    }
    public void intiRegisters(os.Process proc){
        
        getMode().setRegisterValue(VM.mode+"");
        getPTR().setRegisterValue(proc.regs.get("PTR"));
        getPI().setRegisterValue(proc.regs.get("PI"));
        getTI().setRegisterValue(proc.regs.get("TI"));
        getSI().setRegisterValue(proc.regs.get("SI"));
        getPC().setRegisterValue(proc.regs.get("PC"));
        getSP().setRegisterValue(proc.regs.get("SP"));
        getR().setRegisterValue(proc.regs.get("R"));
        getC().setRegisterValue(proc.regs.get("C"));
        
        //registerArray.add(new Register("CH[1]" , 0, 1));
        //registerArray.add(new Register("CH[2]" , 0, 1));
        //registerArray.add(new Register("CH[3]" , 0, 1));
    
    }
    /**
     * Registrų getteriai
     * @see getMode, getPTR, getPI, getTI, getSI, getPC, getSP, getR, getC, getCH1, getCH2, getCH3
     * @author Šarūnas Kūjalis
     */
    public Register getMode() {
        return registerArray.get(0);
    }
    
    public Register getPTR() {
        return registerArray.get(1);
    }
    
    public Register getPI() {
        return registerArray.get(2);
    }
    
    public Register getTI() {
        return registerArray.get(3);
    }
    
    public Register getSI() {
        return registerArray.get(4);
    }
    
    public Register getPC() {
        return registerArray.get(5);
    }
    
    public Register getSP() {
        return registerArray.get(6);
    }
    
    public Register getR() {
        return registerArray.get(7);
    }
    
    public Register getC() {
        return registerArray.get(8);
    }
    
    public Register getCH1() {
        return registerArray.get(9);
    }
    
    public Register getCH2() {
        return registerArray.get(10);
    }
    
    public Register getCH3() {
        return registerArray.get(11);
    }
    public Register getCH4() {
        return registerArray.get(12);
    }

    /**
     * Iš virtualaus adreso grąžina ląstelės turinį
     * @author Paulius Dambrauskas
     * @param address
     * @return word
     */
    private String getByVirtualAddress(int address){
        return Memory.getByAddress(getRealAddress(address));
    }
   
    /**
     * Grąžina registrų sąrašą
     * @author Šarūnas Kūjalis
     * @return registers
     */
    public ObservableList<Register> getRegisters() {
        return registerArray;
    }
   
    /**
     * Funkcija, skirta gaut atminties daliai, kurioj yra VM
     * @author Šarūnas Kūjalis
     * @return data - virtualios atminties listas 
     */
    public ObservableList<Word> getVirtualMemory(){
        ObservableList<Word> data = FXCollections.observableList(new ArrayList<Word>());

        for (int i = 0; i < 16; i++){
            for(int j = 0; j < 16; j++){
                data.add(Memory.memory.get(getRealAddress(i*16+j)));
            }

        }
        return data;
    }
    /**
     * Vietoj Virtualaus - Realus
     * @author Paulius Dambrauskas
     * @param virtualAdress virtualus adresas
     * @return realAddress realus adresas [PTR*16+x]*16+y
     */
    public int getRealAddress(int virtualAdress) {
        int ptr = getPTR().getIntVal();
        int blockNr = virtualAdress / 16;
        int wordNr = virtualAdress % 16;
        int block = Integer.parseInt(Memory.memory.get(ptr * 16 + blockNr).wordValue.get(), 16);
        int realAddress = block * 16 + wordNr;
        return realAddress;
        
    }
   
    /**
     * Funkcija, atpažįstanti komandą
     * @author Paulius Dambrauskas
     * @parms command viena iš programos teksto eiluciu
     */
    public void command(String command){
       
        switch (command.toLowerCase()){
            //Aritmetika
            case "addd":{
                sum();
                break;
            }
            case "subb":{
                sub();
                break;
            }
            case "halt":{
                halt();
                break;
            }
            //IO
            case "prin":{
                print();
                break;
            }
            case "read":{
                read();
                break;
            }
            //Stekas    
            case "popp":{
                pop();
                break;
            }
            case "cmpr":{
                cmpr();
                break;
            }    
            case "push":{
                push();
                break;
            }
            case "ligh":{
                light();
                break;
            }
            
            default :{
                if(command.length() == 4){
                    String operation = command.substring(0,2).toLowerCase();
                    String vars = command.substring(2,command.length());
                    switch(operation){
                       case "lw":{
                           loadWord(vars);
                           break;
                       }
                       case "sw":{
                           setWord(vars);
                           break;
                       }
                       //Programos paleidimas
                       case "st":{
                           start(vars);
                           break;
                       }
                       case "jm":{
                           jump(vars);
                           break;
                       }
                       case "jl":{
                           jl(vars);
                          break;
                       }
                       case "je":{
                           je(vars);
                           break;
                       }
                       case "sr":{
                           setR(vars);
                           break;
                       }
                       default :{
                           getPI().setRegisterValue("1");
                           break;
                       }    
                    }
                } else {
                    getPI().setRegisterValue("2");
                }
            
            }
            
        }
    }
    private void light(){
        getSI().setRegisterValue("5");
    }
    /**
     * Į R registrą įrašo dvi raides
     * @param parm 
     */
    private void setR(String parm){
        this.getR().setRegisterValue(parm);
    }
    /**
     * Palyginimo komanda, lygina du viršutinius steko elementus, rezultatą
     * grąžina į C registrą
     * @author Paulius Dambrauskas
     */
    private void cmpr(){
        int result = 0;
        int first = Integer.parseInt(getByVirtualAddress(getSP().getIntVal()));
        pop();
        int second = Integer.parseInt(getByVirtualAddress(getSP().getIntVal()));
        //pop();
        if(first > second)
            result = 1;
        else if (first < second)
            result = 2;
            
        getC().setRegisterValue(result+"");
    }
    /**
     * Sumuoja viršutinius steko elementus, rezultatą į R registrą įrašo 
     * @author Paulius Dambrauskas
     */
    private void sum(){
        int sum = Integer.parseInt(getByVirtualAddress(getSP().getIntVal()));
        pop();
        sum += Integer.parseInt(getByVirtualAddress(getSP().getIntVal()));
        pop();
        getR().setRegisterValue(sum+"");
    }
    /**
     * Dviejų viršutinių steko elementų skirtumą įrašo į R registrą
     * @author Paulius Dambrauskas
     */
    private void sub(){
        int sub = Integer.parseInt(getByVirtualAddress(getSP().getIntVal()));
        pop();
        sub -= Integer.parseInt(getByVirtualAddress(getSP().getIntVal()));
        pop();
        getR().setRegisterValue(sub+"");
    }
    /**
     * Nutraukia programą
     * @author Paulius Dambrauskas
     */
    private void halt(){
        getSI().setRegisterValue("4"); 
    }
    /**
     * Įvykdo pertraukimą su SI  = 2 reikšme
     * @author Paulius Dambrauskas
     */
    private void print(){
        getSI().setRegisterValue("2");
        getCH2().setRegisterValue("1");
        getTI().setRegisterValue(Integer.parseInt(getTI().registerValue.get())-2+"");
    }
    /**
     * Įvykdo pertraukimą su SI = 3 reikšme
     * @author Paulius Dambrauskas
     */
    private void read(){
        getSI().setRegisterValue("3");
        getCH1().setRegisterValue("1");
        getTI().setRegisterValue(Integer.parseInt(getTI().registerValue.get())-2+"");
       // System.out.println("read.");
    }
    /**
     * Į steką įdeda elementą, esantį R registre
     * @author Paulius Dambrauskas
     */
    private void push(){
        String stackItem = getR().registerValue.get();
        getSP().setRegisterValue((getSP().getIntVal()+1)+"");
        Memory.setMemory(getRealAddress(getSP().getIntVal()), stackItem);
    }
    /**
     * Iš steko pašalina elementą ir įdeda jį į R registrą
     * @author Paulius Dambrauskas
     */
    private void pop(){
        String stackItem = getByVirtualAddress(getSP().getIntVal());
        Memory.setMemory(getRealAddress(getSP().getIntVal()), null);
        getSP().setRegisterValue((getSP().getIntVal()-1)+"");
        getR().setRegisterValue(stackItem);
    }
    /**
     * Pagal nurodytą address iš atminties į R registrą įdeda žodį
     * @author Paulius Dambrauskas
     * @param address 
     */
    private void loadWord(String address){
        getR().setRegisterValue(getByVirtualAddress(Memory.hexToDec(address)));
    }
    /**
     * Nustato atminties žodį pagal address iš R registro
     * @author Paulius Dambrauskas
     * @param address 
     */
    private void setWord(String address){
        Memory.setMemory(getRealAddress(Memory.hexToDec(address)), getR().registerValue.get());
    }
    /**
     * Paleidžia programą, kurios pavadinimas ir parametrai yra bloke paga address
     * @param address 
     */
    private void start(String address){
        try{
            getR().setRegisterValue(getRealAddress(Memory.hexToDec(address))+"");
            getSI().setRegisterValue("1");
            getCH3().setRegisterValue("1");
            getTI().setRegisterValue(Integer.parseInt(getTI().registerValue.get())-2+"");
        } catch(Exception e){
            getPI().setRegisterValue("2");  
        }
    }
    /**
     * Šoka į komandą, esančią address
     * @author Paulius Dambrauskas
     * @param address 
     */
    private void jump(String address){
        try{
            getPC().setRegisterValue(Memory.hexToDec(address)+"");
            //getPC().registerValue.set(Memory.hexToDec(address)+"");
            //System.out.println("jump"+Memory.hexToDec(address));
        } catch(Exception e){
            getPI().setRegisterValue("2");   
        }
    }
    /**
     * Jei registras C = 2 šoka į komandą, esančią address
     * @see jump
     * @author Paulius Dambrauskas
     * @param address 
     */
    private void jl(String address){
        if(getC().registerValue.get().equals("2"))
            jump(address);
     
    }
    /**
     * Jei registras C = 1 šoka į komandą, esančią adresu address
     * @see jump
     * @author Paulius Dambrauskas
     * @param address 
     */
    private void je(String address){
        if(getC().registerValue.get().equals("1"))
            jump(address);
    }
    
    /**
     * Funkcija, įvykdanti visą virtualią programą
     * @author Paulius Dambrauskas
     */
    public void run(){
        boolean step = step();
        while(step){
            step = step();
        }
    }
    
    /**
     * Funkcija, atliekanti vieną virtualios programos žingsnį
     * @author Paulius Dambrauskas
     * @return boolean ar interruptas
     */
    public boolean step(){
            int pc = Integer.parseInt(getPC().registerValue.get());
            String command = getByVirtualAddress(pc);
            try{
                pc++;
                getPC().setRegisterValue(pc+"");
                command(command);
            } catch (Exception e){
                getPI().setRegisterValue("2");
            }
            /*pc++;
            getPC().setRegisterValue(pc+"");*/
            getTI().setRegisterValue(Integer.parseInt(getTI().registerValue.get())-1+"");
           
            
            if(checkInterrupt()){
                OS.processes.get(OS.currentProcess).runProcess();                
                return false;
            } else
                return true;
      
    }
  
    /**
     * Tikrina ar neįvyko pertraukimas
     * @author Paulius Dambrauskas
     * @return boolean
     */
    private boolean checkInterrupt(){
        int c = 0;
        if(getMode().registerValue.get().equals("1")){
            return true;
        }
        
        if(!getSI().registerValue.get().equals("0")){
            createInterrupt("SI", getSI().getIntVal());
            return true;
            //c++;
        }        
        if(!getPI().registerValue.get().equals("0")){
            createInterrupt("PI", getPI().getIntVal());
            return true;
            //c++;
        }
        if(getTI().getIntVal() <= 0){
            createInterrupt("TI", getTI().getIntVal());
            return true;
            //c++;
        }
        /*if(c > 0){
            //OS.readyList.add(OS.currentProcess);//?
            return true;
        }*/

        return false;
            
        
    }
    private void createInterrupt(String type, int value){
        getMode().setRegisterValue("1");
        
        VM.mode = 1;
        ResourceElement e = new ResourceElement("Pertraukimo",new String[]{type, value+"", OS.currentProcess+""});
        OS.freeRes(e);

    }
    public void endInterrupt(){
        getMode().setRegisterValue("0");
        getSI().setRegisterValue("0");
        getPI().setRegisterValue("0");
    }

}
